#include <QCoreApplication>
#include <QTimer>
#include <QSerialPort>
#include <QThread>
#include <QDateTime>
#include <QTextCodec>
#include <QImage>
#include <QPixmap>
#include <QDebug>

#include "setting/JSON_settings.h"
#include "coordinator/coordinator.h"
#include "printer/prt_controller.h"

#define MultiByteEncoding_GBK		0
#define MultiByteEncoding_UTF8		1
#define MultiByteEncoding_BIG5		3
#define MultiByteEncoding_ShiftJIS	4
#define MultiByteEncoding_EUCKR		5

#define Alignment_Left				0
#define Alignment_HCenter			1
#define Alignment_Right				2

#define Status_NoPaper				0x0c

PrtController_Worker::PrtController_Worker(QObject *parent)
	: QObject(parent)
{
	m_port = nullptr;
	m_read_timer = nullptr;
	m_check_timer = nullptr;
    m_logo = nullptr;
	m_online = false;
	m_gb_codec = QTextCodec::codecForName("GB18030");
}

PrtController_Worker::~PrtController_Worker()
{
	stop_work();
}

void PrtController_Worker::start_work()
{
	m_port = new QSerialPort(m_param.port, this);
	if (m_port) {
		m_port->setBaudRate(m_param.baud);
        m_port->setDataBits(m_param.data_bits);
		m_port->setStopBits(m_param.stop_bits);
		m_port->setParity(m_param.parity);
		if (!m_port->open(QIODevice::ReadWrite)) {
            qInfo() << "[PRT]Open Serial Port " << m_param.port << " Failed!";
            delete m_port;
            m_port = nullptr;
            QTimer::singleShot(10000, this, &PrtController_Worker::start_work);
			return;
		}
	}

    qInfo() << "[PRT]Connecting Printer Module at " << m_param.port;
    qInfo() << "[PRT]    Baud: " << m_param.baud;
    qInfo() << "[PRT]DataBits: " << m_param.data_bits;
    qInfo() << "[PRT]  Parity: " << m_param.parity;
    qInfo() << "[PRT]StopBits: " << m_param.stop_bits;

    m_data.clear();
	m_read_timer = new QTimer(this);
	m_check_timer = new QTimer(this);

    connect(m_read_timer, &QTimer::timeout, this, &PrtController_Worker::receive_data);
    m_read_timer->start(200);
    connect(m_check_timer, &QTimer::timeout, this, &PrtController_Worker::check_online);
    m_check_timer->start(500);
}

void PrtController_Worker::stop_work()
{
	if (m_read_timer)
		delete m_read_timer;
	if (m_port)
		delete m_port;
	if (m_check_timer)
		delete m_check_timer;
	m_check_timer = nullptr;
	m_read_timer = nullptr;
	m_port = nullptr;
}

void PrtController_Worker::setSerialParam(SerialConfig param)
{
	PrtController_Worker::m_param = param;
}

void PrtController_Worker::receive_data()
{
	if (m_port)
		m_data += m_port->readAll();
}

void PrtController_Worker::check_online()
{
	uint32_t status;
	bool online = false;

	status = 0;
	while (m_data.length()) {
		status = m_data[0];
		if (status == 0x60)
			online = true;
		m_data.remove(0, 1);
	}

	m_online = online;
	emit online_status(online);
    emit paper_status((status | Status_NoPaper) == 0);

    inquiry();
}

void PrtController_Worker::transfer_data(QByteArray data)
{
	if (m_port)
		m_port->write(data);
}

QByteArray PrtController_Worker::rcsv_analyze(QByteArray &raw)
{
	const uint8_t postfix[] = { 0xff, 0xfc, 0xff, 0xff };
	QByteArray ba_postfix((char *) postfix, sizeof(postfix));
    QByteArray resp;

	while (raw.length() >= 6) {
        if ((uint8_t) raw[0] != 0xee) {
        	qInfo() << "[PRT]Dispose data" << QString("%1").arg(raw[0], 2, 16, QLatin1Char('0'));
			raw.remove(0, 1);
			continue;
		}
		int postfix_pos = raw.indexOf(ba_postfix);
		if (postfix_pos == -1)
            return resp;
		// a complete command sequence found!
        resp = raw.mid(0, postfix_pos + sizeof(postfix));
		raw.remove(0, postfix_pos + sizeof(postfix));
	}

    return resp;
}

void PrtController_Worker::parse_msg(QByteArray msg)
{
	Q_UNUSED(msg);
}

void PrtController_Worker::reset()
{
	const uint8_t cmd[] = { 0x1b, 0x40 };
	QByteArray ba_cmd((char *) cmd, sizeof(cmd));

	transfer_data(ba_cmd);
}

void PrtController_Worker::inquiry()
{
	const uint8_t cmd[] = { 0x1d, 0x72, 0x31 };
	QByteArray ba_cmd((char *) cmd, sizeof(cmd));

	transfer_data(ba_cmd);
}

void PrtController_Worker::set_encoding(uint32_t encoding)
{
	const uint8_t cmd[] = { 0x1b, 0x39, 0x00 };
	QByteArray ba_cmd((char *) cmd, sizeof(cmd));
	ba_cmd[2] = encoding & 0xff;

	transfer_data(ba_cmd);
}

void PrtController_Worker::set_alignment(uint32_t alignment)
{
	const uint8_t cmd[] = { 0x1b, 0x61, 0x00 };
	QByteArray ba_cmd((char *) cmd, sizeof(cmd));
	ba_cmd[2] = alignment & 0xff;

	transfer_data(ba_cmd);
}

void PrtController_Worker::set_bold(bool bold)
{
	const uint8_t cmd[] = { 0x1b, 0x45, 0x00 };
	QByteArray ba_cmd((char *) cmd, sizeof(cmd));
	ba_cmd[2] = (bold) ? 1 : 0;

	transfer_data(ba_cmd);
}

void PrtController_Worker::set_double_width(bool double_width)
{
	const uint8_t cmd[] = { 0x1b, 0x21, 0x00 };
	QByteArray ba_cmd((char *) cmd, sizeof(cmd));
	ba_cmd[2] = (double_width) ? 0x20 : 0x00;

	transfer_data(ba_cmd);
}

void PrtController_Worker::prt_logo()
{
	if (m_logo == nullptr)
		return;

	QSize size = m_logo->size();
	int width = size.width() & 0xfff8;
	const uint8_t cmd[] = { 0x1d, 0x76, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00 };
	QByteArray ba_cmd((char *) cmd, sizeof(cmd));
	ba_cmd[4] = (width / 8) & 0xff;
	ba_cmd[5] = (width / 8) >> 8;
	ba_cmd[6] = size.height() & 0xff;
	ba_cmd[7] = size.height() >> 8;

	for (int y = 0; y < size.height(); y++) {
		uint8_t data = 0;
		for (int x = 0; x < width; x++) {
			data <<= 1;
			if (qGray(m_logo->pixel(x, y)) < 128)
				data |= 1;
			if ((x % 8) == 7) {
				ba_cmd.append(data ^ 0xff);
				data = 0;
			}
		}
	}

    transfer_data(ba_cmd);
}

void PrtController_Worker::prt_qrcode(TransInfo trans_info)
{
    set_alignment(Alignment_HCenter); set_bold(true);

	const uint8_t cmd[] = { 0x1d, 0x6b, 0x61, 0x08, 0x02, 0x00, 0x00 };
	QByteArray ba_cmd((char *) cmd, sizeof(cmd));

	QString info;
	QByteArray ba_info;
	info = trans_info.terminal_id;
	info += QString(" %1%2").arg(trans_info.date_time.toString("yyyyMMddhhmmss")).arg(trans_info.id, 4, 10, QLatin1Char('0'));
	ba_info = info.toLocal8Bit();
	ba_cmd[5] = ba_info.length() & 0xff;
	ba_cmd[6] = ba_info.length() >> 8;
	ba_cmd.append(ba_info);

	transfer_data(ba_cmd);
}

#define LABEL_report_type			QObject::tr("*** Report Type ***")
#define LABEL_client_name			QObject::tr("Client Name: ")
#define LABEL_terminal_name			QObject::tr("Terminal Name: ")
#define LABEL_terminal_id			QObject::tr("Terminal ID: ")
#define LABEL_deposit_category		QObject::tr("Deposit Category: ")
#define LABEL_deposit_class			QObject::tr("Deposit Class: ")
#define LABEL_user_name				QObject::tr("User Name: ")
#define LABEL_user_type				QObject::tr("User Type: ")
#define LABEL_date					QObject::tr("Date: ")
#define LABEL_seqno					QObject::tr("Seq No: ")
#define LABEL_note_type				QObject::tr("Note Type")
#define LABEL_note_count			QObject::tr("Note Count")
#define LABEL_note_amount			QObject::tr("Amount")
#define LABEL_total 				QObject::tr("Total:")

void PrtController_Worker::print_transaction(TransInfo trans_info)
{
	QByteArray ba_buff;
	QString date;

	date = trans_info.date_time.toString("yyyy-MM-dd hh:mm:ss");

	reset();
    set_encoding(MultiByteEncoding_GBK);

    set_alignment(Alignment_HCenter); set_bold(true);
//	prt_logo();
    ba_buff.clear();
    ba_buff.append("\n");
    ba_buff.append(m_gb_codec->fromUnicode(Coordinator::coordinator()->get_vendor_name())); ba_buff.append("\n\n");
    ba_buff.append(m_gb_codec->fromUnicode(LABEL_report_type)); ba_buff.append("\n\n");
    transfer_data(ba_buff);

    set_alignment(Alignment_Left); set_bold(false);
    ba_buff.clear();
    ba_buff.append(m_gb_codec->fromUnicode(LABEL_client_name));
    ba_buff.append(m_gb_codec->fromUnicode(trans_info.client_name)); ba_buff.append("\n");
    ba_buff.append(m_gb_codec->fromUnicode(LABEL_terminal_name));
    ba_buff.append(m_gb_codec->fromUnicode(trans_info.terminal_name)); ba_buff.append("\n");
    ba_buff.append(m_gb_codec->fromUnicode(LABEL_terminal_id));
    ba_buff.append(m_gb_codec->fromUnicode(trans_info.terminal_id)); ba_buff.append("\n");
    ba_buff.append(m_gb_codec->fromUnicode(LABEL_deposit_category));
    ba_buff.append(m_gb_codec->fromUnicode(Coordinator::coordinator()->category_description(trans_info.note_info.trans_type / 10 - 1))); ba_buff.append("\n");
    ba_buff.append(m_gb_codec->fromUnicode(LABEL_deposit_class));
    ba_buff.append(m_gb_codec->fromUnicode(((trans_info.note_info.trans_type % 2) == TRANS_CLASS_NOTE) ? tr("Note") : tr("Coin"))); ba_buff.append("\n");
    ba_buff.append(m_gb_codec->fromUnicode(LABEL_user_name));
    ba_buff.append(m_gb_codec->fromUnicode(trans_info.user_name)); ba_buff.append("\n");
    ba_buff.append(m_gb_codec->fromUnicode(LABEL_user_type));
    ba_buff.append(m_gb_codec->fromUnicode(trans_info.user_alias)); ba_buff.append("\n");
    ba_buff.append(m_gb_codec->fromUnicode(LABEL_date));
    ba_buff.append(date.toLatin1()); ba_buff.append("\n");
    ba_buff.append(m_gb_codec->fromUnicode(LABEL_seqno));
    date.remove(QRegExp("[ :-]"));
    ba_buff.append(date.toLatin1());
    ba_buff.append(QString("%1").arg(trans_info.id, 4, 10, QLatin1Char('0')).toLatin1()); ba_buff.append("\n\n");
    transfer_data(ba_buff);

    ba_buff.clear();
    ba_buff.append(m_gb_codec->fromUnicode(LABEL_note_type)); ba_buff.append(6, ' ');
    ba_buff.append(m_gb_codec->fromUnicode(LABEL_note_count)); ba_buff.append(14, ' ');
    ba_buff.append(m_gb_codec->fromUnicode(LABEL_note_amount)); ba_buff.append("\n");
    for (int k = 0; k < NOTE_TYPE_COUNT; k++) {
    	uint32_t count, amount;
    	count = trans_info.note_info.note_count[k];
    	amount = trans_info.note_info.note_amount[k];
    	if (trans_info.note_info.note_count[k] == 0)
    		continue;
        QByteArray note_type = m_gb_codec->fromUnicode(NOTE_name(k));
    	ba_buff.append(note_type); ba_buff.append(6 - note_type.length(), ' ');
        ba_buff.append(QString("%1").arg(count, 8, 10).toLatin1());
        ba_buff.append(QString("%1").arg(amount / 100.0, 18, 'f', 2).toLatin1());
	    ba_buff.append("\n");
    }
    transfer_data(ba_buff);

    ba_buff.clear();
    ba_buff.append(32, '-'); ba_buff.append("\n");
    ba_buff.append(m_gb_codec->fromUnicode(LABEL_total));
    ba_buff.append(QString("%1").arg(trans_info.note_info.total_count, 8, 10).toLatin1());
    ba_buff.append(QString("%1").arg(trans_info.note_info.total_amount / 100.0, 18, 'f', 2).toLatin1());
	ba_buff.append("\n\n");
    transfer_data(ba_buff);

    prt_qrcode(trans_info);

    ba_buff.clear();
	ba_buff.append("  \n\n\n");
    transfer_data(ba_buff);
}

#define LABEL_time					QObject::tr("Time")
#define LABEL_amount				QObject::tr("Amount")
#define LABEL_total_amount			QObject::tr("Total")
#define LABEL_accumulated_amount	QObject::tr("Accumlated Total")

void PrtController_Worker::print_transactions(QList<TransInfo> trans_list)
{
	QByteArray ba_buff;
	uint32_t total_amount, sub_total_amount;
	QList<uint32_t> category_list;
	QString date;

	reset();
    set_encoding(MultiByteEncoding_GBK);

    set_alignment(Alignment_HCenter); set_bold(true);
//	prt_logo();
    ba_buff.clear();
    ba_buff.append("\n");
    ba_buff.append(m_gb_codec->fromUnicode(Coordinator::coordinator()->get_vendor_name())); ba_buff.append("\n\n");
    ba_buff.append(m_gb_codec->fromUnicode(LABEL_report_type)); ba_buff.append("\n\n");
    transfer_data(ba_buff);

    if (trans_list.count()) {
    	TransInfo trans_info;

    	trans_info = trans_list.at(0);
	    set_alignment(Alignment_Left); set_bold(false);
	    ba_buff.clear();
	    ba_buff.append(m_gb_codec->fromUnicode(LABEL_client_name));
	    ba_buff.append(m_gb_codec->fromUnicode(trans_info.client_name)); ba_buff.append("\n");
	    ba_buff.append(m_gb_codec->fromUnicode(LABEL_terminal_name));
	    ba_buff.append(m_gb_codec->fromUnicode(trans_info.terminal_name)); ba_buff.append("\n");
	    ba_buff.append(m_gb_codec->fromUnicode(LABEL_terminal_id));
	    ba_buff.append(m_gb_codec->fromUnicode(trans_info.terminal_id)); ba_buff.append("\n");
	    ba_buff.append(m_gb_codec->fromUnicode(LABEL_user_name));
	    ba_buff.append(m_gb_codec->fromUnicode(trans_info.user_name)); ba_buff.append("\n");
	    ba_buff.append(m_gb_codec->fromUnicode(LABEL_user_type));
	    ba_buff.append(m_gb_codec->fromUnicode(trans_info.user_alias)); ba_buff.append("\n");
	    transfer_data(ba_buff);
    }

    foreach(auto trans_info, trans_list) {
    	uint32_t category = trans_info.note_info.trans_type / 10 - 1;
    	if (!category_list.contains(category))
    		category_list << category;
    }

    total_amount = 0;
    foreach(auto category, category_list) {
	    set_alignment(Alignment_Left); set_bold(true);
	    ba_buff.clear();
    	ba_buff.append(32, '-'); ba_buff.append("\n");
	    ba_buff.append(m_gb_codec->fromUnicode(LABEL_deposit_category));
	    ba_buff.append(m_gb_codec->fromUnicode(Coordinator::coordinator()->category_description(category))); ba_buff.append("\n");
    	ba_buff.append(32, '-'); ba_buff.append("\n");
	    transfer_data(ba_buff);
	    ba_buff.clear();
	    ba_buff.append(m_gb_codec->fromUnicode(LABEL_time));
	    ba_buff.append(24, ' ');
	    ba_buff.append(m_gb_codec->fromUnicode(LABEL_amount));
		ba_buff.append("\n");
	    transfer_data(ba_buff);

	    set_alignment(Alignment_Left); set_bold(false);
	    sub_total_amount = 0;
	    foreach(auto trans_info, trans_list) {
			uint32_t amount;

			if (trans_info.note_info.trans_type / 10 - 1 != category)
				continue;
	    	ba_buff.clear();
	    	amount = trans_info.note_info.total_amount;
	    	sub_total_amount += amount;
	    	date = trans_info.date_time.toString("yyyy-MM-dd hh:mm:ss");
	    	ba_buff.append(date.toLatin1());
		    ba_buff.append(QString("%1").arg(amount / 100.0, 13, 'f', 2).toLatin1());
			ba_buff.append("\n");
		    transfer_data(ba_buff);
	    }

	    ba_buff.clear();
	    ba_buff.append(m_gb_codec->fromUnicode(LABEL_total_amount));
	    ba_buff.append(16, ' ');
		ba_buff.append(QString("%1").arg(sub_total_amount / 100.0, 12, 'f', 2).toLatin1());
		ba_buff.append("\n");
	    transfer_data(ba_buff);

	    total_amount += sub_total_amount;
    }

    ba_buff.clear();
    ba_buff.append(32, '-'); ba_buff.append("\n");
    ba_buff.append(m_gb_codec->fromUnicode(LABEL_accumulated_amount));
    ba_buff.append(16, ' ');
	ba_buff.append(QString("%1").arg(total_amount / 100.0, 12, 'f', 2).toLatin1());
	ba_buff.append("  \n\n\n");
    transfer_data(ba_buff);
	reset();
}

#define LABEL_clr_report_type		QObject::tr("*** Clear Report Type ***")
#define LABEL_clr_date				QObject::tr("Date: ")
#define LABEL_clr_note_count		QObject::tr("Note Count:")
#define LABEL_clr_coin_count		QObject::tr("Coin Count:")
#define LABEL_clr_amount			QObject::tr("Total Amount:")

void PrtController_Worker::print_clr_transaction(ClearInfo clr_info)
{
	QByteArray ba_buff;
	QString date;

    date = clr_info.date_time.toString("yyyy-MM-dd hh:mm:ss");

	reset();
    set_encoding(MultiByteEncoding_GBK);

    set_alignment(Alignment_HCenter); set_bold(true);
//	prt_logo();
    ba_buff.clear();
    ba_buff.append("\n");
    ba_buff.append(m_gb_codec->fromUnicode(Coordinator::coordinator()->get_vendor_name())); ba_buff.append("\n\n");
    ba_buff.append(m_gb_codec->fromUnicode(LABEL_clr_report_type)); ba_buff.append("\n\n");
    transfer_data(ba_buff);

    set_alignment(Alignment_Left); set_bold(false);
    ba_buff.clear();
    ba_buff.append(m_gb_codec->fromUnicode(LABEL_clr_date));
    ba_buff.append(date.toLatin1()); ba_buff.append("\n");
    ba_buff.append(m_gb_codec->fromUnicode(LABEL_clr_note_count));
    ba_buff.append(QString("%1").arg(clr_info.note_count, 8, 10).toLatin1()); ba_buff.append("  ");
    ba_buff.append(QString("%1").arg(clr_info.note_amount / 100.0, 13, 'f', 2).toLatin1()); ba_buff.append("\n");
    ba_buff.append(m_gb_codec->fromUnicode(LABEL_clr_coin_count));
    ba_buff.append(QString("%1").arg(clr_info.coin_count, 8, 10).toLatin1()); ba_buff.append("  ");
    ba_buff.append(QString("%1").arg(clr_info.coin_amount / 100.0, 13, 'f', 2).toLatin1()); ba_buff.append("\n");
    ba_buff.append(m_gb_codec->fromUnicode(LABEL_clr_amount));
    ba_buff.append("          ");
    ba_buff.append(QString("%1").arg(clr_info.amount / 100.0, 13, 'f', 2).toLatin1()); ba_buff.append("\n");
    ba_buff.append("\n\n\n");
    transfer_data(ba_buff);
}

PrtController::PrtController(QObject *parent)
{
    QSettings settings(JSONSettings::GetSettingFilePath(), JSONSettings::GetFormat(), QCoreApplication::instance());
    PrtController_Worker::SerialConfig param;
    QString logo_file;

    param.port = settings.value(JSON_PRINTER_DEV).toString();
    param.baud = settings.value(JSON_PRINTER_BAUD).toUInt();
    param.data_bits = (QSerialPort::DataBits) settings.value(JSON_PRINTER_DATABITS).toUInt();
    param.stop_bits = (QSerialPort::StopBits) settings.value(JSON_PRINTER_STOPBITS).toUInt();
    param.parity = (QSerialPort::Parity) settings.value(JSON_PRINTER_PARITY).toUInt();

    logo_file = settings.value(JSON_WORK_LOGO_FILE).toString();

	m_coordinator = qobject_cast<Coordinator *>(parent);

	m_worker = new PrtController_Worker();
	m_worker->setSerialParam(param);
	if (!logo_file.isEmpty()) {
		QImage *logo = new QImage(logo_file);
		if (!logo->isNull())
			m_worker->setLogoImage(logo);
		else
			delete logo;
	}
	m_worker->moveToThread(&m_worker_thread);

	connect(&m_worker_thread, &QThread::finished, m_worker, &QObject::deleteLater);
    connect(this, &PrtController::start, m_worker, &PrtController_Worker::start_work);
    connect(this, &PrtController::stop, m_worker, &PrtController_Worker::stop_work);

    m_worker_thread.setObjectName("Printer Thread");
	m_worker_thread.start();
}

PrtController::~PrtController()
{
	m_worker_thread.quit();
	m_worker_thread.wait();
}
